package com.cenntro.common.redis;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;


/**
 * @desc resdis service
 */
@Service
public class RedisServiceImpl implements IRedisService {


	private Logger LOG = LoggerFactory.getLogger(RedisServiceImpl.class);
	private long expires = 20*1000;
	@Autowired
	private RedisTemplate<String, Object> redisTemplate;

	/**
	 * 
	 * @param key
	 * @param value
	 * @param expire 秒
	 * @return
	 */
	public boolean set(final String key, final Object value, long... expire) {
	    if(expire != null && expire.length > 0){
	        redisTemplate.opsForValue().set(key, value, expire[0], TimeUnit.SECONDS);
	    }else {
	        redisTemplate.opsForValue().set(key, value);
	    }
		return Boolean.TRUE;
	}
	
	/**
	 * redis取值
	 */
	public <T> T get(final String key, Class<T> clazz) {
		Object result = redisTemplate.opsForValue().get(key);
		return JSON.parseObject(JSON.toJSONString(result), clazz);
	}
	/**
	 * 设置过期
	 */
	@Override
	public boolean expire(final String key, long expire) {
		return redisTemplate.expire(key, expire, TimeUnit.SECONDS);
	}

	/**
	 * 删除一个key
	 * @param key
	 */
	@Override
	public void remove(final String key) {
		redisTemplate.delete(key);
	}
	/**
	 * Deprecated 用get方法代替
	 */
	@Deprecated
	@Override
	public <T> T getObject(final String key, Class<T> clazz) {
		String json = get(key, String.class);
		if (json != null) {
			return JSONObject.parseObject(json, clazz);
		}
		return null;
	}
	/**
	 * Deprecated 用set方法代替
	 */
	@Deprecated
	@Override
	public Object getObject(final String key) {
		//方式1：.在redistemplate中配置Serializer
		return redisTemplate.execute(new RedisCallback<byte[]>() {
			@Override
			public byte[] doInRedis(RedisConnection connection) throws DataAccessException {
				byte[] rkey = redisTemplate.getStringSerializer().serialize(key);
				if(connection.exists(rkey)){
					byte[] value = connection.get(rkey);
					return value;
				}
				return null;
			}
		});
	}
	/**
	 * Deprecated 用set方法代替
	 */
	@Deprecated
	public void setObject(final String key, byte[] obj, Long... ttl) {
		redisTemplate.execute(new RedisCallback<Boolean>() {
			@Override
			public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
				byte[] rkey = redisTemplate.getStringSerializer().serialize(key);
				connection.set(rkey, obj);
				return Boolean.TRUE;
			}
		});
		
		if (ArrayUtils.isNotEmpty(ttl)){
			this.expire(key, ttl[0]);
		}
	}
	/**
	 * Deprecated 用set方法代替
	 */
	@Deprecated
	@Override
	public Boolean setObject(final String key, Object obj, final Long...ttl) {
		if (obj == null){
			return false;
		}
		
		boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
			@Override
			public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
				RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
				connection.set(serializer.serialize(key), serializer.serialize(JSONObject.toJSONString(obj)));
				return true;
			}
		});
		if (ArrayUtils.isNotEmpty(ttl)){
			this.expire(key, ttl[0]);
		}
		return true;
	}
	
	
	/**
	 * key
	 * value 超时时间 System.currentTimeMillis() + expireMsecs + 1;
	 */
	@Override
	public boolean tryLock(final String key) {
		String expiresStr = String.valueOf(System.currentTimeMillis() + expires + 1);
		if (this.setNX(key, expiresStr)){
			return true;
		}
		int timeout = 2 * 1000;
		int loop = 50;
		try {
			while(timeout > 0){
				LOG.info("tryLock try");
				String currentValueStr = this.get(key, String.class); 
		        if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
		            String oldValueStr = this.getSet(key, expiresStr);
		            LOG.info("oldValueStr :" + oldValueStr);
		            //只有一个线程才能获取上一个线上的设置时间
		            if (currentValueStr.equals(oldValueStr)) {
		                return true;
		            }
		        }
		        timeout -= loop;
		        Thread.sleep(1);
			}
		} catch (Exception e) {
			LOG.error("tryLock redis error, key : {}", key, e);
		}
		
		return false;
	}
	
	
	/**
	 * setnx
	 * @param key
	 * @param value
	 * @return
	 */
	@Override
	public boolean setNX(final String key, final Object value) {
		return redisTemplate.opsForValue().setIfAbsent(key, value);
	}
	
	/**
	 * 
	 * @param key
	 * @param value
	 * @return
	 */
	@Override
	 public <T> T getSet(final String key, final T value) {
		return (T)redisTemplate.opsForValue().getAndSet(key, value);
    }
	
	/**
	 * hash取值
	 */
	@Override
	public <T> T hget(String key, String field) {
		return redisTemplate.<String, T>opsForHash().get(key, field);
	}
	/**
	 * 设置hash
	 */
	@Override
	public <T> void hset(String key, String field, T value) {
		redisTemplate.<String, T>opsForHash().put(key, field, value);
	}
	
	@Override
	public <T> Map<String, T> hmget(String key) {
		return redisTemplate.<String, T>opsForHash().entries(key);
	}
	
	@Override
	public <T> void hmset(String key, Map<String, T> map) {
		redisTemplate.opsForHash().putAll(key, map);
	}
	
	
	
	@Override
	public void sendMessage(String channel, String message) {
		redisTemplate.convertAndSend(channel, message);
	}
	
	@Override
	public Set<String> keys(String key) {
		if (StringUtils.isBlank(key)) {
			return new HashSet<String>();
		}
		Set<String> keyset = redisTemplate.keys(key);
		return keyset;
	}
	
	@Override
	public Long dbSize() {
		return redisTemplate.execute(new RedisCallback<Long>() {
		    @Override
		    public Long doInRedis(RedisConnection connection) throws DataAccessException {
		        return connection.dbSize();
		    }
        });
	}
}
